﻿using QueuesLib;
using System;
using System.Collections.Generic;
using System.Data.Objects.SqlClient;
using System.Linq;
using System.Web;

namespace QueuesServer {
    public class QueuesManager {
        private TicketGenerator ticketGen;
        private QueuesData.QueuesDataAdapter queuesData;

        public QueuesManager() {
            ticketGen = new TicketGenerator();
            queuesData = new QueuesData.QueuesDataAdapter();
        }

        public Ticket CallNext(string workStationId, int subjectId, out QueueInfo info) {
            using (var db = new QueuesData.QueuesDBEntities()) {

                //test subject validity
                if (subjectId != Subject.ANY_SUBJECT) {
                    var subject = db.CurrentSubjects.Find(subjectId);
                    if (subject == null) {
                        throw new ArgumentException("Unknown subject");
                    }
                }

                // find WorkStation
                var workStation = GetWorkStation(workStationId);
                EndAttendings(workStation.Id);

                var tickets = db.PendingTickets.AsQueryable();

                if (subjectId != Subject.ANY_SUBJECT) {
                    tickets = tickets.Where(s => s.SubjectId == subjectId);
                }
                tickets.OrderBy(s => s.CreationTime);

                //get next Ticket
                if (tickets.Count() == 0) {
                    throw new InvalidOperationException("Empty queue");
                }

                QueuesData.PendingTicket pend = tickets.First();
                var sub = GetSubject(pend.SubjectId); //throws exception if Subject doesn't exist

                QueuesData.Attending att = new QueuesData.Attending();
                att.TicketId = pend.Id;
                att.WorkStationId = workStation.Id;
                att.StartTime = DateTime.Now;

                db.Attendings.Add(att);
                db.SaveChanges();
                var ticket = new Ticket() { Id = pend.Id, CreationTime = pend.CreationTime, Number = pend.Number, Text = pend.Text };

                info = new QueueInfo(sub.Id, sub.Name, workStation.Id, workStation.Name, ticket);
                return ticket;
            }
        }

        private QueuesData.CurrentSubject GetSubject(int SubjectId) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                var Subjects = from a in db.CurrentSubjects
                               where a.Id == SubjectId
                               select a;

                if (Subjects.Count() == 1) {
                    return Subjects.First();
                }

                throw new ArgumentException("Unknown Subject");
            }
        }

        private QueuesData.Client GetWorkStation(string workStationId) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                var workStation = db.Clients.Find(workStationId);
                if (workStation == null) {
                    throw new UnauthorizedAccessException("Unknown WorkStation");
                }
                return workStation;
            }
        }

        public void EndAttendings(string workStationId) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                //get Tickets being previously attended by that WorkStation
                var currs = from se in db.CurrentTickets
                            where se.WorkStationId == workStationId
                            select se;

                foreach (var curr in currs) {
                    //get Attendings of that Ticket (it should be only 1)
                    var attends = from a in db.Attendings
                                  where a.Id == curr.AttendingId
                                  select a;

                    //attending is over: set EndTime for that attending
                    foreach (var attend in attends) {
                        attend.EndTime = DateTime.Now;
                    }
                }
                db.SaveChanges();
            }
        }

        public Ticket NewTicket(int SubjectId) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                var subjects = from a in db.CurrentSubjects
                               where a.Id == SubjectId
                               select a;

                if (subjects.Count() == 1) {
                    var sub = subjects.First();
                    var s = ticketGen.NextTicket(subjects.First());
                    return new Ticket() { Id = s.Id, Number = s.Number, Text = s.Text, CreationTime = s.CreationTime, SubjectName = sub.Name };
                }
            }
            throw new ArgumentException("Unknown Subject");
        }

        public SubjectInfo GetSubjectInfo(int subjectId) {
            var info = new SubjectInfo();
            using (var db = new QueuesData.QueuesDBEntities()) {
                var SubjectDb = db.CurrentSubjects.Find(subjectId);

                if (SubjectDb == null) {
                    throw new ArgumentException("Unknown Subject");
                }

                info.Subject = new Subject(SubjectDb.Id, SubjectDb.Name, SubjectDb.Mnemonic);

                //var countDb = db.Counters.Where(c => c.SubjectId == subjectId);
                info.Count = db.Counters.Sum(c => c.Count) ?? 0;
                var curr = queuesData.QueuesCurrentState.Where(s => s.SubjectId == SubjectDb.Id).FirstOrDefault();
                info.CurrentTicketText = (curr == null ? "" : curr.TicketText);
                info.NextTicketText = SubjectDb.Mnemonic + (ticketGen.LastTicketNumber(SubjectDb) + 1).ToString("000");
                info.WaitingTime = GetWaitingTime(subjectId);

            }
            return info;
        }

        private TimeSpan GetWaitingTime(int SubjectId) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                long totalTicks = 0L;
                long media = 0L;
                var attendings = from a in db.Attendings
                                 where a.Ticket.SubjectId == SubjectId
                                 && a.EndTime != null
                                 select new { Start = a.Ticket.CreationTime, End = a.StartTime };

                if (attendings.Count() > 0) {
                    foreach (var att in attendings) {
                        totalTicks += att.Start.Ticks - att.End.Ticks;
                    }

                    media = totalTicks / attendings.Count();
                }
                int front = db.PendingTickets.AsQueryable().Where(s => s.SubjectId == SubjectId).Count();

                return new TimeSpan(media * front);
            }
        }
    }
}